package com.rtsapp.server.logger.spi;

import com.rtsapp.server.logger.appender.*;
import com.rtsapp.server.logger.async.AsyncLoggerThread;
import com.rtsapp.server.logger.async.AsyncLoggerTooMuchExecption;
import com.rtsapp.server.logger.format.*;

import java.io.FileInputStream;
import java.io.InterruptedIOException;
import java.util.*;
import java.util.concurrent.CountDownLatch;

/**
 * 解析日志配置
 * 不是线程安全的, 建议每次解析前都创建一个LoggerConfig
 * 分两步, 解析和创建分开
 */
public class LoggerConfig {

    static final String     THREAD_PREFIX = "log4j.thread";
    static final String     LOGGER_PREFIX   = "log4j.logger.";
    static final String ROOT_LOGGER_PREFIX   = "log4j.rootLogger";
    static final String     APPENDER_PREFIX = "log4j.appender.";

    /**
     * 异步线程数量
     */
    private int threadSize = 0 ;
    /**
     * 所有的layout信息
     *  layout可能重复配置多次, 要想办法去重
     */
    private final Map<LayoutInfo, LayoutInfo> layoutInfoMap = new HashMap<>();

    /**
     * 所有的appender信息
     */
    private final Map<String, AppenderInfo> appenderInfoMap = new HashMap<>();

    /**
     * rootLogger
     */
    private CategoryInfo rootCategoryInfo;

    /**
     * 所有其他root
     */
    private List<CategoryInfo> allCategoryInfos = new ArrayList<>( );




    public LoggerRepository createRepository( ) throws ConfigInvalidException {

        //1.如果是异步, 创建所有的线程
        boolean isAsync = threadSize > 0;
        AsyncLoggerThread threads[] = null;
        CountDownLatch countDownLatch = null;
        if( isAsync ){

            threads = new AsyncLoggerThread[threadSize];
            countDownLatch = new CountDownLatch( threadSize );

            for( int i = 0; i < threadSize; i++ ){
                threads[i] = new AsyncLoggerThread( countDownLatch );
            }
        }

        // ==== 按依赖关系反向创建对象  ===

        // 2. 创建Layout( key:layoutinfo, value:layout )
        Map<LayoutInfo, ILayout> layoutMap = new HashMap<>();
        for( LayoutInfo layoutInfo : layoutInfoMap.values() ){
            ILayout layout = createLayout( layoutInfo );
            layoutMap.put( layoutInfo, layout );
        }

        // 3. 创建原始Appender( key:name, value:appender)
        Map<String,ILoggerAppender> appenderMap = new HashMap<>();
        for( AppenderInfo appenderInfo : appenderInfoMap.values() ){
            ILoggerAppender appender = createAppender( appenderInfo );
            appenderMap.put( appenderInfo.getAppenderName() , appender );
        }

        // 4. 创建Appender封装( AsyncLoggerAppender 或 SyncLoggerAppender, AsyncLoggerAppender 需要注册到异步线程中 )
        Map<String,ILoggerAppender> wrapAppendMap = new HashMap<>();
        int threadIndex = 0;
        for( Map.Entry<String,ILoggerAppender> entry : appenderMap.entrySet() )
        {
            wrapAppendMap.put( entry.getKey(), createDecoratorLogger( entry.getValue(), threads, threadIndex++ ) );
        }

        // 5. 创建Category
        LoggerCategory rootCategory =  createCategory( rootCategoryInfo , wrapAppendMap, layoutMap );

        // 6.  Map<String, LoggerCategory>
        Map<String, LoggerCategory> allCategorys = new HashMap<>();
        for( CategoryInfo loggerInfo : allCategoryInfos ){
            LoggerCategory category = createCategory( loggerInfo,  wrapAppendMap, layoutMap  );
            allCategorys.put( loggerInfo.getName(), category );
        }

        // 7. ====最后组装成Repository====
        LoggerRepository repository = new LoggerRepository( countDownLatch, threads, rootCategory, allCategorys );

        return repository;
    }

    private LoggerCategory createCategory(CategoryInfo categoryInfo, Map<String,ILoggerAppender> appenderMap,  Map<LayoutInfo, ILayout> layoutMap ) throws ConfigInvalidException {

        Map<ILayout, List<ILoggerAppender> > coupMap = new HashMap<>();
        Map<ILayout, List<LogLevel> > coupLevelMap = new HashMap<>();

        for( AppenderInfo appenderInfo :  categoryInfo.getAppenderInfos() ){

            ILayout layout =  layoutMap.get( appenderInfo.getLayoutInfo() );
            ILoggerAppender appender = appenderMap.get( appenderInfo.getAppenderName() );
            if( layout == null || appender == null ){
                throw new ConfigInvalidException( "appender:" + appenderInfo.getAppenderName() + "配置有错" );
            }


            List<ILoggerAppender> appenderList =  coupMap.get( layout );
            if( appenderList == null ){
                appenderList = new ArrayList<>( );
                coupMap.put( layout, appenderList);

                coupLevelMap.put( layout, new ArrayList<>() );
            }
            List<LogLevel> levelList = coupLevelMap.get( layout );

            appenderList.add( appender );
            levelList.add( appenderInfo.getThreshold() );
        }

        LoggerCategory.LayoutAppendersCouple couples[] = new LoggerCategory.LayoutAppendersCouple[ coupMap.size() ];
        int i = 0;
        for(  Map.Entry<ILayout, List<ILoggerAppender> > entry :  coupMap.entrySet() ){

            ILayout layout = entry.getKey();
            ILoggerAppender appenders[] = entry.getValue().toArray(new ILoggerAppender[0]);
            LogLevel levels[] = coupLevelMap.get( entry.getKey() ).toArray( new LogLevel[0] );

            couples[ i ] = new LoggerCategory.LayoutAppendersCouple( layout, appenders, levels );
        }

        LoggerCategory category = new LoggerCategory( categoryInfo.getLevel(), couples );
        return category;
    }


    private ILoggerAppender createDecoratorLogger(ILoggerAppender appender, AsyncLoggerThread[] threads, int threadIndex) throws ConfigInvalidException {

        if( threads != null && threads.length > 0 ){
            try {
                AsyncLoggerAppender app =  new AsyncLoggerAppender<>( appender );
                threads[ threadIndex % threads.length ].register( app );
                return app;
            } catch (AsyncLoggerTooMuchExecption asyncLoggerTooMuchExecption) {
                throw new ConfigInvalidException( "线程管理的配置文件太多:" );
            }

        }else{
            SyncLoggerAppender app =  new SyncLoggerAppender<>( appender );
            return app;
        }

    }


    private ILoggerAppender createAppender(AppenderInfo appenderInfo) throws ConfigInvalidException {

        String className =  appenderInfo.getAppenderClass();
        if( className.equals( ConsoleAppender.class.getName() ) ){
            return new ConsoleAppender( );

        }else if( className.equals( RollingFileAppender.class.getName() ) ){

            String file =  appenderInfo.getProps().get( "File" );
            return new RollingFileAppender(  file, "yyyy-MM-dd" );

        }else{
            throw new ConfigInvalidException ( "createAppender出错, 没有找到对应的Appender" );
        }
    }


    /**
     * 创建一个Layout
     * 目前仅支持PatternLayout
     * @param layoutInfo
     * @return
     */
    private ILayout createLayout(LayoutInfo layoutInfo) {

        // TODO 先忽略这个配置格式, 按统一的格式写
        //String pattern =  layoutInfo.getPattern( );
        ILogFormat formats[] = {
                new DateFormat( "yyyy-MM-dd HH:mm:ss" ),
                new LiteralFormat( " [" ),
//                new LevelFomat(),
                new ThreadFormat(),
                new LiteralFormat( "] " ),
//                new LineFormat(),
                new MessageFormat(),
                new LiteralFormat( "\r\n" )
        };

        PatternLayout layout = new PatternLayout( formats );
        return layout;
    }




    public boolean parseConfig( String configFileName ){

        Properties props = new Properties();
        FileInputStream istream = null;
        try{
            istream = new FileInputStream( configFileName );
            props.load( istream );
            istream.close();
        }catch( Exception e ){
            if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
                Thread.currentThread().interrupt();
            }
            LogLog.error( "Could not read configuration file [" + configFileName + "].", e);
            LogLog.error("Ignoring configuration file [" + configFileName + "].");
            return false;
        }finally {
            if(istream != null) {
                try {
                    istream.close();
                } catch(InterruptedIOException ignore) {
                    Thread.currentThread().interrupt();
                } catch(Throwable ignore) {
                    LogLog.error( "关闭配置文件"+ configFileName +"出错:", ignore );
                }
            }
        }

        try {
            doConfigure( props );
        } catch (ConfigInvalidException e) {
            LogLog.error( "解析配置文件出错", e );
            return false;
        }

        if( ! validate() ){
            return false;
        }

        return true;
    }


    private  void doConfigure(Properties properties ) throws ConfigInvalidException {

        // 解析线程数量
        this.threadSize = Integer.parseInt( properties.getProperty( THREAD_PREFIX , "0") );


        // 解析rootLogger, 这个必须要有
        rootCategoryInfo = configCategory(properties, ROOT_LOGGER_PREFIX, "root" );

        // 解析其他的logger: log4j.logger
        for( Object keyObj :  properties.keySet() ){
            String key = keyObj.toString();
            if( key.startsWith( LOGGER_PREFIX ) ){
                String logName = key.substring( LOGGER_PREFIX.length() );
                CategoryInfo categoryInfo = configCategory( properties, key, logName );
                allCategoryInfos.add( categoryInfo );
            }
        }


    }






    private CategoryInfo configCategory( Properties properties, String propKey, String categoryName ) throws ConfigInvalidException {

        String paramString = properties.getProperty(propKey);
        if( paramString == null ){
            throw new ConfigInvalidException( propKey + "配置为空" );
        }

        String[] params = paramString.split( "," );
        if( params.length < 2 ){
            throw new ConfigInvalidException( propKey + "配置有错, 参数个数不能小于2" );
        }

        // level
        LogLevel level = LogLevel.toLevel(params[0].trim());
        if( level == null ){
            throw new ConfigInvalidException( propKey + "配置有错, 没有日志级别:" + params[ 0 ] );
        }

        // appender
        AppenderInfo appenders[] = new AppenderInfo[ params.length - 1 ];
        for( int i = 1; i < params.length; i++ ){
            appenders[ i - 1] = configAppender( properties, params[i].trim() );
        }

        return new CategoryInfo( categoryName, level, appenders );
    }


    private  AppenderInfo configAppender( Properties properties, String simpleName ) throws ConfigInvalidException {

        String appenderName =  APPENDER_PREFIX + simpleName;

        AppenderInfo appenderInfo =  appenderInfoMap.get(appenderName);
        if( appenderInfo != null ){
            return appenderInfo;
        }


        String appenderClass = properties.getProperty( appenderName );
        String prefix = appenderName + ".";

        // 所有的附加属性
        String threshold = null;

        Map<String,String> propMap = new HashMap<>( );
        String layoutClass = null;
        String layoutPattern = null;

        for( Object key : properties.keySet() ){

            String prop = key.toString();
            if( prop.startsWith( prefix ) ){

                if( prop.equals( prefix + "Threshold" ) ){
                    threshold = properties.getProperty(prop);
                }else if( prop.equals(  prefix + "layout" ) ){
                    layoutClass = properties.getProperty( prop );
                }else if( prop.equals(prefix + "layout.ConversionPattern") ){
                    layoutPattern =  properties.getProperty( prop ) ;
                }else{
                    propMap.put( prop.substring( prefix.length() ), properties.getProperty( prop ) );
                }
            }
        }

        //TODO 暂时注释这行, 目前只支持一种格式
//        if(  layoutPattern == null ){
//            throw new ConfigInvalidException( prefix + "layout.ConversionPattern 不能为空" );
//        }


        //创建或获取原有的Layout
        LayoutInfo layoutInfo = new LayoutInfo( "com.rtsapp.server.logger.format.PatternLayout", layoutPattern );
        LayoutInfo oldLayoutInfo  = layoutInfoMap.putIfAbsent(layoutInfo, layoutInfo);
        if( oldLayoutInfo != null ){
            layoutInfo = oldLayoutInfo;
        }



        LogLevel thresholdLevel = LogLevel.toLevel( threshold );
        if( thresholdLevel == null ){
            throw new ConfigInvalidException( "threshold有错" );
        }

        appenderInfo = new AppenderInfo( appenderName, appenderClass, thresholdLevel,  layoutInfo, propMap );
        appenderInfoMap.put( appenderName , appenderInfo );

        return appenderInfo;
    }




    private boolean validate(){
        return true;
    }


    private static class CategoryInfo {
        private final String name;
        private final LogLevel level;
        private final AppenderInfo[] appenderInfos;

        public CategoryInfo(String name, LogLevel level, AppenderInfo[] appenderNames) {
            this.name = name;
            this.level = level;
            this.appenderInfos = appenderNames;
        }

        public String getName() {
            return name;
        }

        public LogLevel getLevel() {
            return level;
        }

        public AppenderInfo[] getAppenderInfos() {
            return appenderInfos;
        }

    }


    private static class AppenderInfo{

        private final String appenderName;
        private final  String appenderClass;
        private final  LogLevel threshold;
        private final  LayoutInfo layoutInfo;
        private final  Map<String,String> props;

        public AppenderInfo(String appenderName, String appenderClass, LogLevel threshold, LayoutInfo layoutInfo, Map<String,String> props ) {
            this.appenderName = appenderName;
            this.appenderClass = appenderClass;
            this.threshold = threshold;
            this.layoutInfo = layoutInfo;
            this.props = props;
        }

        public String getAppenderName() {
            return appenderName;
        }

        public String getAppenderClass() {
            return appenderClass;
        }

        public LogLevel getThreshold() {
            return threshold;
        }

        public Map<String, String> getProps() {
            return props;
        }

        public LayoutInfo getLayoutInfo() {
            return layoutInfo;
        }

    }


    private static class LayoutInfo{

        //现在只支持PatternLayout
        private final String className;
        private final String pattern;

        public LayoutInfo(String className, String pattern) {
            this.className = className;
            this.pattern = pattern;
        }

        public String getClassName() {
            return className;
        }

        public String getPattern() {
            return pattern;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            LayoutInfo that = (LayoutInfo) o;

            if (className != null ? !className.equals(that.className) : that.className != null) return false;
            return !(pattern != null ? !pattern.equals(that.pattern) : that.pattern != null);

        }

        @Override
        public int hashCode() {
            int result = className != null ? className.hashCode() : 0;
            result = 31 * result + (pattern != null ? pattern.hashCode() : 0);
            return result;
        }
    }



}
